#include "FL/Fl.H"
#include "FL/Fl_Double_Window.H"
#include "FL/Fl_Button.H"
#include "FL/Fl_Input.H"
#include "FL/Fl_Multiline_Input.H"
#include "FL/Fl_Box.H"
#include "FL/Fl_Pack.H"

// 以下文件包含的是常见的显示消息的函数，例如
// void fl_alert(const char *fmt, ...), 显示信息,像MessageBox
// int fl_ask(const char *fmt, ...), 问问题(返回1或0)
// void fl_beep(int type), beep一下
// 这些函数都很方便(printf类型的)，但是为什么不放到Fl.H这个大杂烩类呢?
// 可见Fltk的API还有诸多不合理之处
#include "FL/fl_ask.H"

class EscapeChar; // 见下文

// Fltk中最方便的就是继承已有的类，进行各种自定义
class CommentDialog : public Fl_Double_Window
{
public:
    // 继承的构造函数格式也基本是固定的
    CommentDialog(int x, int y, int w, int h, const char *title)
        : Fl_Double_Window(x, y, w, h, title)
    {
        CreateUI();
        size_range(w, h, 0, 0);
        resizable(_comment);
        // 对于Window来说，它的Callback就是当它被关闭时要调用的函数
        // 这对于实现文档有改变，问用户想不想继续之类很有用
        this->callback(gcallback, (void *)this);
    }
    // 这是Fltk标准的也是唯一的回调函数，第一个是指向触发回调的控件，
    // 第二个是在绑定回调时设置的任意参数。
    //
    // 使用面向对象的风格时，因为不能把类成员函数作为回调函数，所以一般使用这种方法：
    // 在父控件的类中声明一个静态回调函数，所有的子控件需要设置回调时
    // 都使用这个回调，而这个回调的第二个参数可以设为
    // 父控件类实例的指针，通过这个指针调用相应的方法
    static void gcallback(Fl_Widget *w, void *data)
    {
        CommentDialog *d = (CommentDialog *)data;
        if (w == d->_name->child(1))
            d->OnNameCheck(w);
        else if (w == d->_mail->child(1))
            d->OnMailCheck(w);
        else
            d->OnOk(w); // “好的”按钮或者直接关闭窗口
    }

    void OnNameCheck(Fl_Widget *w);
    void OnMailCheck(Fl_Widget *w);
    void OnOk(Fl_Widget *w);

    const char *GetName() const { return ((Fl_Input *)_name->child(0))->value(); }
    const char *GetMail() const { return ((Fl_Input *)_mail->child(0))->value(); }
    const char *GetComment() const { return _comment->value(); }

private:
    void CreateUI();
    Fl_Pack *CreatePack(int y, const char *item, const char *button);

    Fl_Pack *_name;
    Fl_Pack *_mail;
    Fl_Pack *_ok;
    Fl_Multiline_Input *_comment;
};

void CommentDialog::CreateUI()
{
    _name = CreatePack(10, "姓名:", "检查!");
    _mail = CreatePack(50, "邮件:", "检查!");
    _comment = new Fl_Multiline_Input(50, 100, w() - 60, h() - 150, "评论:");
    _ok = CreatePack(h() - 40, NULL, "好的");
}

Fl_Pack *CommentDialog::CreatePack(int y, const char *item, const char *button)
{
    Fl_Pack *pack = new Fl_Pack(50, y, w() - 20, 30);
    pack->type(Fl_Pack::HORIZONTAL);
    pack->spacing(10);
    Fl_Widget *itemw;
    if (item)
        itemw = new Fl_Input(0, 0, w() - 175, 30, item);
    else
        itemw = new Fl_Box(0, 0, w() - 175, 30);
    Fl_Button *b = new Fl_Button(0, 0, 100, 30, button);
    b->callback(CommentDialog::gcallback, (void *)this);
    pack->end();

    pack->resizable(itemw);
    return pack;
}
// 以下的检查代码只是把输入显示出来！因为本次主要讲回调函数的设置，
// 所以没有实现真正的检查代码。注意Fltk很方便的显示信息的函数
void CommentDialog::OnNameCheck(Fl_Widget *w)
{
    EscapeChar name(GetName());
    fl_alert("您好: %s!", (const char *)name);
}
void CommentDialog::OnMailCheck(Fl_Widget *w)
{
    EscapeChar name(GetName());
    EscapeChar mail(GetMail());
    fl_alert("%s, 您好! 您的Email是:\n%s!",
             (const char *)name,
             (const char *)mail);
}
void CommentDialog::OnOk(Fl_Widget *w)
{
    EscapeChar name(GetName());
    EscapeChar comment(GetComment());
    fl_alert("%s 认为:\n%s\n多谢您的评论!",
             (const char *)name,
             (const char *)comment);
    // 当最后一个窗口隐藏时，Fltk自动退出
    hide();
}

int main(int argc, char **argv)
{
    CommentDialog *dlg = new CommentDialog(100, 200, 460, 320, "FltkCallback");
    dlg->show();
    return Fl::run();
}